/*
 * Copyright (c) gitee.com/jiandong001.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import ArrayList from '@ohos.util.ArrayList';

import { NumberUtil } from './YogaUtils';
import { YogaConfigNAPIBase } from './YogaConfigNAPIBase';
import { getYogaMeasureModeFromNumber } from './YogaMeasureMode';
import { getYogaAlignFromNumber } from './YogaAlign';
import { getYogaPositionTypeFromNumber } from './YogaPositionType';
import { getYogaDisplayFromNumber } from './YogaDisplay';
import { getYogaOverflowFromNumber } from './YogaOverflow';
import { getYogaWrapFromNumber } from './YogaWrap';
import { getYogaJustifyFromNumber } from './YogaJustify';
import { getYogaFlexDirectionFromNumber } from './YogaFlexDirection';
import { Inputs } from './YogaNode';
import { YogaNative } from './YogaNative';
import { YogaBaselineFunction } from './YogaBaselineFunction';
import { YogaMeasureFunction } from './YogaMeasureFunction';
import { YogaNode } from './YogaNode';
import { YogaOverflow } from './YogaOverflow';
import { YogaValue } from './YogaValue';
import { YogaPositionType } from './YogaPositionType';
import { YogaFlexDirection } from './YogaFlexDirection';
import { YogaAlign } from './YogaAlign';
import { YogaDirection } from './YogaDirection';
import { getYogaDirectionFromNumber } from './YogaDirection';
import { YogaJustify } from './YogaJustify';
import { YogaConfig } from './YogaConfig';
import { YogaEdge } from './YogaEdge';
import { YogaDisplay } from './YogaDisplay';
import { YogaWrap } from './YogaWrap';


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//package com.facebook.yoga;
//
//import com.facebook.proguard.annotations.DoNotStrip;
//import java.util.ArrayList;
//import java.util.List;
//import javax.annotation.Nullable;

//@DoNotStrip
export abstract class YogaNodeNAPIBase extends YogaNode /*implements Cloneable*/ {

  /* Those flags needs be in sync with YGNAPI.h */
  private static readonly MARGIN = 1;
  private static readonly PADDING = 2;
  private static readonly BORDER = 4;
  private static readonly DOES_LEGACY_STRETCH_BEHAVIOUR = 8;
  private static readonly HAS_NEW_LAYOUT = 16;

  private static readonly LAYOUT_EDGE_SET_FLAG_INDEX = 0;
  private static readonly LAYOUT_WIDTH_INDEX = 1;
  private static readonly LAYOUT_HEIGHT_INDEX = 2;
  private static readonly LAYOUT_LEFT_INDEX = 3;
  private static readonly LAYOUT_TOP_INDEX = 4;
  private static readonly LAYOUT_DIRECTION_INDEX = 5;
  private static readonly LAYOUT_MARGIN_START_INDEX = 6;
  private static readonly LAYOUT_PADDING_START_INDEX = 10;
  private static readonly LAYOUT_BORDER_START_INDEX = 14;

  private mOwner?: YogaNodeNAPIBase;
  private mChildren?: ArrayList<YogaNodeNAPIBase> ;
  private mMeasureFunction?: YogaMeasureFunction;
  private mBaselineFunction?: YogaBaselineFunction;
  protected mNativePointer: number; // long
  private mData?: Object;

  private arr: number[] = null; //@DoNotStrip
  private mLayoutDirection: number = 0; //@DoNotStrip
  private mHasNewLayout: boolean = true;

//  private constructor(long nativePointer) {
//    if (nativePointer == 0) {
//      throw new IllegalStateException("Failed to allocate native memory");
//    }
//    mNativePointer = nativePointer;
//  }
//
//  constructor() {
//    this(YogaNative.napi_YGNodeNewNAPI());
//  }
//
  constructor(config?: YogaConfig) {
    super()
    this.mNativePointer = YogaNative.napi_YGNodeNewWithConfigNAPI((config as YogaConfigNAPIBase).getNativePointer());
  }

  public reset(): void {
    this.mMeasureFunction = null;
    this.mBaselineFunction = null;
    this.mData = null;
    this.arr = null;
    this.mHasNewLayout = true;
    this.mLayoutDirection = 0;

    YogaNative.napi_YGNodeResetNAPI(this.mNativePointer);
  }

  public getChildCount(): number {
    return !this.mChildren? 0 : this.mChildren.length;
  }

  public getChildAt(i: number): YogaNodeNAPIBase | never {
    if (this.mChildren == null) {
      throw new Error("YogaNode does not have children");
    }
    return this.mChildren[i];
  }

  public addChildAt(c: YogaNode, i: number) {
    if (!(c instanceof YogaNodeNAPIBase)) {
      return;
    }
    let child: YogaNodeNAPIBase = c as YogaNodeNAPIBase;
    if (child.mOwner != null) {
      throw new Error("Child already has a parent, it must be removed first.");
    }

    if (this.mChildren == null) {
      this.mChildren = new ArrayList<YogaNodeNAPIBase>();
    }
    this.mChildren.insert(child, i)
    child.mOwner = this;
    YogaNative.napi_YGNodeInsertChildNAPI(this.mNativePointer, child.mNativePointer, i);
  }

  public setIsReferenceBaseline(isReferenceBaseline: boolean) {
    YogaNative.napi_YGNodeSetIsReferenceBaselineNAPI(this.mNativePointer, isReferenceBaseline);
  }

  public isReferenceBaseline(): boolean {
    return YogaNative.napi_YGNodeIsReferenceBaselineNAPI(this.mNativePointer);
  }

  public swapChildAt(newChild: YogaNode, position: number) {
    if (!(newChild instanceof YogaNodeNAPIBase)) {
      return;
    }
    let child = newChild as YogaNodeNAPIBase;
    this.mChildren.removeByIndex(position)
    this.mChildren.insert(child, position);
    child.mOwner = this;
    YogaNative.napi_YGNodeSwapChildNAPI(this.mNativePointer, child.mNativePointer, position);
  }

   // @Override -> ts4.3 override
   public cloneWithChildren(): YogaNodeNAPIBase { // YogaNodeNAPIBase
    return null
//    try {
//      YogaNodeNAPIBase clonedYogaNode = (YogaNodeNAPIBase) super.clone();
//      if (clonedYogaNode.mChildren != null) {
//        clonedYogaNode.mChildren = new ArrayList<>(clonedYogaNode.mChildren);
//      }
//      long clonedNativePointer = YogaNative.napi_YGNodeCloneNAPI(this.mNativePointer);
//      clonedYogaNode.mOwner = null;
//      clonedYogaNode.mNativePointer = clonedNativePointer;
//      for (int i = 0; i < clonedYogaNode.getChildCount(); i++) {
//        clonedYogaNode.swapChildAt(clonedYogaNode.getChildAt(i).cloneWithChildren(), i);
//      }
//
//      return clonedYogaNode;
//    } catch (CloneNotSupportedException ex) {
//      // This class implements Cloneable, this should not happen
//      throw new RuntimeException(ex);
//    }
  }

  // @Override -> ts4.3 override
  public cloneWithoutChildren(): YogaNodeNAPIBase { // YogaNodeNAPIBase
    return null;
//    try {
//      YogaNodeNAPIBase clonedYogaNode = (YogaNodeNAPIBase) super.clone();
//      long clonedNativePointer = YogaNative.napi_YGNodeCloneNAPI(this.mNativePointer);
//      clonedYogaNode.mOwner = null;
//      clonedYogaNode.mNativePointer = clonedNativePointer;
//      clonedYogaNode.clearChildren();
//      return clonedYogaNode;
//    } catch (CloneNotSupportedException ex) {
//      // This class implements Cloneable, this should not happen
//      throw new Error(ex);
  }

  private clearChildren(): void {
    this.mChildren.clear()
    this.mChildren = null;
    YogaNative.napi_YGNodeClearChildrenNAPI(this.mNativePointer);
  }

  public removeChildAt(i: number): YogaNodeNAPIBase { // YogaNodeNAPIBase
    if (this.mChildren == null) {
      throw new Error(
          "Trying to remove a child of a YogaNode that does not have children");
    }
    const child: YogaNodeNAPIBase = this.mChildren.removeByIndex(i);
    child.mOwner = null;
    YogaNative.napi_YGNodeRemoveChildNAPI(this.mNativePointer, child.mNativePointer);
    return child;
  }

  /**
   * The owner is used to identify the YogaTree that a {@link YogaNode} belongs to. This method will
   * return the parent of the {@link YogaNode} when the {@link YogaNode} only belongs to one
   * YogaTree or null when the {@link YogaNode} is shared between two or more YogaTrees.
   *
   * @return the {@link YogaNode} that owns this {@link YogaNode}.
   */
//  @Nullable
  public getOwner(): YogaNodeNAPIBase | null { // YogaNodeNAPIBase
    return this.mOwner;
  }

  /** @deprecated Use #getOwner() instead. This will be removed in the next version. */
//  @Deprecated
//  @Nullable
  public getParent(): YogaNodeNAPIBase | null  { // YogaNodeNAPIBase
    return this.getOwner();
  }

  public indexOf(child: YogaNode): number {
    return this.mChildren == null ? -1 : this.mChildren.getIndexOf(child as YogaNodeNAPIBase);
  }

  public calculateLayout(width: number, height: number): void{
    let nativePointers: number[] = null;
    let nodes: YogaNodeNAPIBase[] = null;

    this.freeze(null);

    let n  = new ArrayList<YogaNodeNAPIBase>();
    n.add(this);

    for (let i = 0; i < n.length; ++i) {
      const parent: YogaNodeNAPIBase = n[i];
      const children = parent.mChildren;
      if (children != null) {
        for (let child of children) {
          child.freeze(parent);
          n.add(child);
        }
      }
    }

    nodes = n.convertToArray();
    nativePointers = new Array<number>(nodes.length); //long
    for (let i = 0; i < nodes.length; ++i) {
      nativePointers[i] = nodes[i].mNativePointer;
    }

    YogaNative.napi_YGNodeCalculateLayoutNAPI(this.mNativePointer, width, height, nativePointers, nodes);
  }

  private isInputs(value: Inputs): value is Inputs { //magic happens here
    return (<Inputs>value).freeze !== undefined;
  }

  private freeze(parent: YogaNode): void {
    let data: Object = this.getData()
    if ("freeze" in data) {
      (data as Inputs).freeze(this, parent);
    } else if (this.isInputs(data as Inputs)) {
      (data as Inputs).freeze(this, parent);
    }
  }

  public dirty(): void {
    YogaNative.napi_YGNodeMarkDirtyNAPI(this.mNativePointer);
  }

  public dirtyAllDescendants(): void {
    YogaNative.napi_YGNodeMarkDirtyAndPropogateToDescendantsNAPI(this.mNativePointer);
  }

  public isDirty(): boolean {
    return YogaNative.napi_YGNodeIsDirtyNAPI(this.mNativePointer);
  }

//   @Override
  public copyStyle(srcNode: YogaNode): void {
    if (!(srcNode instanceof YogaNodeNAPIBase)) {
      return;
    }
    YogaNative.napi_YGNodeCopyStyleNAPI(this.mNativePointer, (srcNode as YogaNodeNAPIBase).mNativePointer);
  }

  public getStyleDirection(): YogaDirection {
    return getYogaDirectionFromNumber(YogaNative.napi_YGNodeStyleGetDirectionNAPI(this.mNativePointer));
  }

  public setDirection(direction: YogaDirection) {
    YogaNative.napi_YGNodeStyleSetDirectionNAPI(this.mNativePointer, direction); // intValue
  }

  public getFlexDirection(): YogaFlexDirection {
    return getYogaFlexDirectionFromNumber(YogaNative.napi_YGNodeStyleGetFlexDirectionNAPI(this.mNativePointer));
  }

  public setFlexDirection(flexDirection: YogaFlexDirection) {
    YogaNative.napi_YGNodeStyleSetFlexDirectionNAPI(this.mNativePointer, flexDirection); // intValue
  }

  public getJustifyContent(): YogaJustify {
    return getYogaJustifyFromNumber(YogaNative.napi_YGNodeStyleGetJustifyContentNAPI(this.mNativePointer));
  }

  public setJustifyContent(justifyContent: YogaJustify): void {
    YogaNative.napi_YGNodeStyleSetJustifyContentNAPI(this.mNativePointer, justifyContent); // intValue
  }

  public getAlignItems(): YogaAlign {
    return getYogaAlignFromNumber(YogaNative.napi_YGNodeStyleGetAlignItemsNAPI(this.mNativePointer));
  }

  public setAlignItems(alignItems: YogaAlign) {
    YogaNative.napi_YGNodeStyleSetAlignItemsNAPI(this.mNativePointer, alignItems); // intValue
  }

  public getAlignSelf(): YogaAlign {
    return getYogaAlignFromNumber(YogaNative.napi_YGNodeStyleGetAlignSelfNAPI(this.mNativePointer));
  }

  public setAlignSelf(alignSelf: YogaAlign) {
    YogaNative.napi_YGNodeStyleSetAlignSelfNAPI(this.mNativePointer, alignSelf); // intValue
  }

  public getAlignContent(): YogaAlign {
    return getYogaAlignFromNumber(YogaNative.napi_YGNodeStyleGetAlignContentNAPI(this.mNativePointer));
  }

  public setAlignContent(alignContent: YogaAlign) {
    YogaNative.napi_YGNodeStyleSetAlignContentNAPI(this.mNativePointer, alignContent); //intValue
  }

  public getPositionType(): YogaPositionType {
    return getYogaPositionTypeFromNumber(YogaNative.napi_YGNodeStyleGetPositionTypeNAPI(this.mNativePointer));
  }

  public setPositionType(positionType: YogaPositionType) {
    YogaNative.napi_YGNodeStyleSetPositionTypeNAPI(this.mNativePointer, positionType); // intValue
  }

  public getWrap(): YogaWrap {
    return getYogaWrapFromNumber(YogaNative.napi_YGNodeStyleGetFlexWrapNAPI(this.mNativePointer));
  }

  public setWrap(flexWrap: YogaWrap) {
    YogaNative.napi_YGNodeStyleSetFlexWrapNAPI(this.mNativePointer, flexWrap); //intValue
  }

  public getOverflow(): YogaOverflow {
    return getYogaOverflowFromNumber(YogaNative.napi_YGNodeStyleGetOverflowNAPI(this.mNativePointer));
  }

  public setOverflow(overflow: YogaOverflow) {
    YogaNative.napi_YGNodeStyleSetOverflowNAPI(this.mNativePointer, overflow);
  }

  public getDisplay(): YogaDisplay {
    return getYogaDisplayFromNumber(YogaNative.napi_YGNodeStyleGetDisplayNAPI(this.mNativePointer));
  }

  public setDisplay(display: YogaDisplay) {
    YogaNative.napi_YGNodeStyleSetDisplayNAPI(this.mNativePointer, display);
  }

  public getFlex(): number {
    return YogaNative.napi_YGNodeStyleGetFlexNAPI(this.mNativePointer);
  }

  public setFlex(flex: number ) {
    YogaNative.napi_YGNodeStyleSetFlexNAPI(this.mNativePointer, flex);
  }

  public getFlexGrow(): number  {
    return YogaNative.napi_YGNodeStyleGetFlexGrowNAPI(this.mNativePointer);
  }

  public setFlexGrow(flexGrow: number) {
    YogaNative.napi_YGNodeStyleSetFlexGrowNAPI(this.mNativePointer, flexGrow);
  }

  public getFlexShrink(): number {
    return YogaNative.napi_YGNodeStyleGetFlexShrinkNAPI(this.mNativePointer);
  }

  public setFlexShrink(flexShrink: number) {
    YogaNative.napi_YGNodeStyleSetFlexShrinkNAPI(this.mNativePointer, flexShrink);
  }

  public getFlexBasis(): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetFlexBasisNAPI(this.mNativePointer));
  }

  public setFlexBasis(flexBasis: number) {
    YogaNative.napi_YGNodeStyleSetFlexBasisNAPI(this.mNativePointer, flexBasis);
  }

  public setFlexBasisPercent(percent: number) {
    YogaNative.napi_YGNodeStyleSetFlexBasisPercentNAPI(this.mNativePointer, percent);
  }

  public setFlexBasisAuto() {
    YogaNative.napi_YGNodeStyleSetFlexBasisAutoNAPI(this.mNativePointer);
  }

  public getMargin(edge: YogaEdge): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetMarginNAPI(this.mNativePointer, edge)); //intValue
  }

  public setMargin(edge: YogaEdge, margin: number) {
    YogaNative.napi_YGNodeStyleSetMarginNAPI(this.mNativePointer, edge, margin); //intValue
  }

  public setMarginPercent(edge: YogaEdge, percent: number) {
    YogaNative.napi_YGNodeStyleSetMarginPercentNAPI(this.mNativePointer, edge, percent);
  }

  public setMarginAuto(edge: YogaEdge) {
    YogaNative.napi_YGNodeStyleSetMarginAutoNAPI(this.mNativePointer, edge);  ///intValue
  }

  public getPadding(edge: YogaEdge): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetPaddingNAPI(this.mNativePointer, edge)); //intValue
  }

  public setPadding(edge: YogaEdge, padding: number) {
    YogaNative.napi_YGNodeStyleSetPaddingNAPI(this.mNativePointer, edge, padding);//intValue
  }

  public setPaddingPercent(edge: YogaEdge, percent: number) {
    YogaNative.napi_YGNodeStyleSetPaddingPercentNAPI(this.mNativePointer, edge, percent);//intValue
  }

  public getBorder(edge: YogaEdge): number {
    return YogaNative.napi_YGNodeStyleGetBorderNAPI(this.mNativePointer, edge);//intValue
  }

  public setBorder(edge: YogaEdge, border: number) {
    YogaNative.napi_YGNodeStyleSetBorderNAPI(this.mNativePointer, edge, border);//intValue
  }

  public getPosition(edge: YogaEdge): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetPositionNAPI(this.mNativePointer, edge));//intValue
  }

  public setPosition(edge: YogaEdge, position: number) {
    YogaNative.napi_YGNodeStyleSetPositionNAPI(this.mNativePointer, edge, position);//intValue
  }

  public setPositionPercent(edge: YogaEdge, percent: number) {
    YogaNative.napi_YGNodeStyleSetPositionPercentNAPI(this.mNativePointer, edge, percent);//intValue
  }

  public getWidth(): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetWidthNAPI(this.mNativePointer));
  }

  public setWidth(width: number) {
    YogaNative.napi_YGNodeStyleSetWidthNAPI(this.mNativePointer, width);
  }

  public setWidthPercent(percent: number) {
    YogaNative.napi_YGNodeStyleSetWidthPercentNAPI(this.mNativePointer, percent);
  }

  public setWidthAuto() {
    YogaNative.napi_YGNodeStyleSetWidthAutoNAPI(this.mNativePointer);
  }

  public getHeight() {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetHeightNAPI(this.mNativePointer));
  }

  public setHeight(height: number) {
    YogaNative.napi_YGNodeStyleSetHeightNAPI(this.mNativePointer, height);
  }

  public setHeightPercent(percent: number) {
    YogaNative.napi_YGNodeStyleSetHeightPercentNAPI(this.mNativePointer, percent);
  }

  public setHeightAuto() {
    YogaNative.napi_YGNodeStyleSetHeightAutoNAPI(this.mNativePointer);
  }

  public getMinWidth(): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetMinWidthNAPI(this.mNativePointer));
  }

  public setMinWidth(minWidth: number) {
    YogaNative.napi_YGNodeStyleSetMinWidthNAPI(this.mNativePointer, minWidth);
  }

  public setMinWidthPercent(percent: number) {
    YogaNative.napi_YGNodeStyleSetMinWidthPercentNAPI(this.mNativePointer, percent);
  }

  public getMinHeight(): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetMinHeightNAPI(this.mNativePointer));
  }

  public setMinHeight(minHeight: number) {
    YogaNative.napi_YGNodeStyleSetMinHeightNAPI(this.mNativePointer, minHeight);
  }

  public setMinHeightPercent(percent: number) {
    YogaNative.napi_YGNodeStyleSetMinHeightPercentNAPI(this.mNativePointer, percent);
  }

  public getMaxWidth(): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetMaxWidthNAPI(this.mNativePointer));
  }

  public setMaxWidth(maxWidth: number) {
    YogaNative.napi_YGNodeStyleSetMaxWidthNAPI(this.mNativePointer, maxWidth);
  }

  public setMaxWidthPercent(percent: number) {
    YogaNative.napi_YGNodeStyleSetMaxWidthPercentNAPI(this.mNativePointer, percent);
  }

  public getMaxHeight(): YogaValue {
    return YogaNodeNAPIBase.valueFromNumber(YogaNative.napi_YGNodeStyleGetMaxHeightNAPI(this.mNativePointer));
  }

  public setMaxHeight(maxheight: number) {
    YogaNative.napi_YGNodeStyleSetMaxHeightNAPI(this.mNativePointer, maxheight);
  }

  public setMaxHeightPercent(percent: number) {
    YogaNative.napi_YGNodeStyleSetMaxHeightPercentNAPI(this.mNativePointer, percent);
  }

  public getAspectRatio(): number {
    return YogaNative.napi_YGNodeStyleGetAspectRatioNAPI(this.mNativePointer);
  }

  public setAspectRatio(aspectRatio: number) {
    YogaNative.napi_YGNodeStyleSetAspectRatioNAPI(this.mNativePointer, aspectRatio);
  }

  public setMeasureFunction(measureFunction: YogaMeasureFunction) {
    this.mMeasureFunction = measureFunction;
    YogaNative.napi_YGNodeSetHasMeasureFuncNAPI(this.mNativePointer, measureFunction != null);
  }

  // Implementation Note: Why this method needs to stay final
  //
  // We cache the jmethodid for this method in Yoga code. This means that even if a subclass
  // were to override measure, we'd still call this implementation from layout code since the
  // overriding method will have a different jmethodid. This is final to prevent that mistake.
//  @DoNotStrip
  public measure(width: number, widthMode: number, height: number, heightMode: number): number { // return long
    if (!this.isMeasureDefined()) {
      throw new Error("Measure function isn't defined!");
    }

    return this.mMeasureFunction.measure(
      this,
      width,
      getYogaMeasureModeFromNumber(widthMode),
      height,
      getYogaMeasureModeFromNumber(heightMode)
    );
  }

  public setBaselineFunction(baselineFunction: YogaBaselineFunction) {
    this.mBaselineFunction = baselineFunction;
    YogaNative.napi_YGNodeSetHasBaselineFuncNAPI(this.mNativePointer, baselineFunction != null);
  }

//  @DoNotStrip
  public baseline(width: number, height: number): number {
    return this.mBaselineFunction.baseline(this, width, height);
  }

  public isMeasureDefined(): boolean {
    return this.mMeasureFunction != null;
  }

//   @Override
  public isBaselineDefined(): boolean {
    return this.mBaselineFunction != null;
  }

  public setData(data: Object) {
    this.mData = data;
  }

//   @Override
  public getData(): Object | null {
    return this.mData;
  }

  /**
   * Use the set logger (defaults to adb log) to print out the styles, children, and computed layout
   * of the tree rooted at this node.
   */
  public print() {
    YogaNative.napi_YGNodePrintNAPI(this.mNativePointer);
  }

  /**
   * This method replaces the child at childIndex position with the newNode received by parameter.
   * This is different than calling removeChildAt and addChildAt because this method ONLY replaces
   * the child in the this.mChildren datastructure. @DoNotStrip: called from NAPI
   *
   * @return the nativePointer of the newNode {@link YogaNode}
   */
//  @DoNotStrip
  private replaceChild(newNode: YogaNodeNAPIBase, childIndex: number): number { //return long
    if (this.mChildren == null) {
      throw new Error("Cannot replace child. YogaNode does not have children");
    }
    this.mChildren.removeByIndex(childIndex);
    this.mChildren.insert(newNode, childIndex);
    newNode.mOwner = this;
    return newNode.mNativePointer;
  }

  //todo: use number to implement long, some error?
  private static valueFromNumber(raw: number): YogaValue {
//    return new YogaValue(Float.intBitsToFloat((number) raw), (number) (raw >> 32));
    return new YogaValue(NumberUtil.intBitsToFloat(raw), raw >> 32);
  }

//   @Override
  public getLayoutX(): number {
    return this.arr != null ? this.arr[YogaNodeNAPIBase.LAYOUT_LEFT_INDEX] : 0;
  }

//   @Override
  public getLayoutY(): number {
    return this.arr != null ? this.arr[YogaNodeNAPIBase.LAYOUT_TOP_INDEX] : 0;
  }

//   @Override
  public getLayoutWidth(): number {
    return this.arr != null ? this.arr[YogaNodeNAPIBase.LAYOUT_WIDTH_INDEX] : 0;
  }

//   @Override
  public getLayoutHeight(): number {
    return this.arr != null ? this.arr[YogaNodeNAPIBase.LAYOUT_HEIGHT_INDEX] : 0;
  }

  public getDoesLegacyStretchFlagAffectsLayout(): boolean {
    return this.arr != null
        && ((this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX] & YogaNodeNAPIBase.DOES_LEGACY_STRETCH_BEHAVIOUR)
            == YogaNodeNAPIBase.DOES_LEGACY_STRETCH_BEHAVIOUR);
  }

//   @Override
  public getLayoutMargin(edge: YogaEdge): number | never {
    if (this.arr != null && (this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX] & YogaNodeNAPIBase.MARGIN) == YogaNodeNAPIBase.MARGIN) {
      switch (edge) {
        case YogaEdge.LEFT:
          return this.arr[YogaNodeNAPIBase.LAYOUT_MARGIN_START_INDEX];
        case YogaEdge.TOP:
          return this.arr[YogaNodeNAPIBase.LAYOUT_MARGIN_START_INDEX + 1];
        case YogaEdge.RIGHT:
          return this.arr[YogaNodeNAPIBase.LAYOUT_MARGIN_START_INDEX + 2];
        case YogaEdge.BOTTOM:
          return this.arr[YogaNodeNAPIBase.LAYOUT_MARGIN_START_INDEX + 3];
        case YogaEdge.START:
          return this.getLayoutDirection() == YogaDirection.RTL
              ? this.arr[YogaNodeNAPIBase.LAYOUT_MARGIN_START_INDEX + 2]
              : this.arr[YogaNodeNAPIBase.LAYOUT_MARGIN_START_INDEX];
        case YogaEdge.END:
          return this.getLayoutDirection() == YogaDirection.RTL
              ? this.arr[YogaNodeNAPIBase.LAYOUT_MARGIN_START_INDEX]
              : this.arr[YogaNodeNAPIBase.LAYOUT_MARGIN_START_INDEX + 2];
        default:
          throw new Error("Cannot get layout margins of multi-edge shorthands");
      }
    } else {
      return 0;
    }
  }

//   @Override
  public getLayoutPadding(edge: YogaEdge): number {
    if (this.arr != null && (this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX] & YogaNodeNAPIBase.PADDING) == YogaNodeNAPIBase.PADDING) {
      let paddingStartIndex: number =
          YogaNodeNAPIBase.LAYOUT_PADDING_START_INDEX
              - ((( this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX] & YogaNodeNAPIBase.MARGIN) == YogaNodeNAPIBase.MARGIN) ? 0 : 4);
      switch (edge) {
        case YogaEdge.LEFT:
          return this.arr[paddingStartIndex];
        case YogaEdge.TOP:
          return this.arr[paddingStartIndex + 1];
        case YogaEdge.RIGHT:
          return this.arr[paddingStartIndex + 2];
        case YogaEdge.BOTTOM:
          return this.arr[paddingStartIndex + 3];
        case YogaEdge.START:
          return this.getLayoutDirection() == YogaDirection.RTL
              ? this.arr[paddingStartIndex + 2]
              : this.arr[paddingStartIndex];
        case YogaEdge.END:
          return this.getLayoutDirection() == YogaDirection.RTL
              ? this.arr[paddingStartIndex]
              : this.arr[paddingStartIndex + 2];
        default:
          throw new Error("Cannot get layout paddings of multi-edge shorthands");
      }
    } else {
      return 0;
    }
  }

//   @Override
  public getLayoutBorder(edge: YogaEdge): number {
    if (this.arr != null && (this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX] & YogaNodeNAPIBase.BORDER) == YogaNodeNAPIBase.BORDER) {
      let borderStartIndex: number =
          YogaNodeNAPIBase.LAYOUT_BORDER_START_INDEX
              - (((this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX] & YogaNodeNAPIBase.MARGIN) == YogaNodeNAPIBase.MARGIN) ? 0 : 4)
              - (((this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX] & YogaNodeNAPIBase.PADDING) == YogaNodeNAPIBase.PADDING) ? 0 : 4);
      switch (edge) {
        case YogaEdge.LEFT:
          return this.arr[borderStartIndex];
        case YogaEdge.TOP:
          return this.arr[borderStartIndex + 1];
        case YogaEdge.RIGHT:
          return this.arr[borderStartIndex + 2];
        case YogaEdge.BOTTOM:
          return this.arr[borderStartIndex + 3];
        case YogaEdge.START:
          return this.getLayoutDirection() == YogaDirection.RTL
              ? this.arr[borderStartIndex + 2]
              : this.arr[borderStartIndex];
        case YogaEdge.END:
          return this.getLayoutDirection() == YogaDirection.RTL
              ? this.arr[borderStartIndex]
              : this.arr[borderStartIndex + 2];
        default:
          throw new Error("Cannot get layout border of multi-edge shorthands");
      }
    } else {
      return 0;
    }
  }

// //   @Override
  public getLayoutDirection(): YogaDirection {
    return getYogaDirectionFromNumber(
        this.arr != null ? this.arr[YogaNodeNAPIBase.LAYOUT_DIRECTION_INDEX] : this.mLayoutDirection);
  }

//   @Override
  public hasNewLayout(): boolean {
    if (this.arr != null) {
      return ((this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX]) & YogaNodeNAPIBase.HAS_NEW_LAYOUT) == YogaNodeNAPIBase.HAS_NEW_LAYOUT;
    } else {
      return this.mHasNewLayout;
    }
  }

//   @Override
  public markLayoutSeen() {
    if (this.arr != null) {
      this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX] = (this.arr[YogaNodeNAPIBase.LAYOUT_EDGE_SET_FLAG_INDEX]) & ~(YogaNodeNAPIBase.HAS_NEW_LAYOUT);
    }
    this.mHasNewLayout = false;
  }
}
